<?php
/*
 * @Author: Leruge 
 * @QQ: 305530751 
 * @Date: 2018-05-16 19:24:34 
 * @Last Modified by:   Leruge 
 * @Last Modified time: 2018-05-16 19:24:34 
 */
namespace dream;

 class DreamChat
 {
    private $master = null;
    private $connectPool = [];
    private $handPool = [];
    private $chatUsers = [];

    public function __construct($port)
    {
        $this->startServer($port);
    }

    public function startServer($port)
    {
        $this->master = socket_create_listen($port);
        if (!$this->master) {
            $this->runInfo("\nERROR : Listening {$port} fail!\n");exit;
        }
        $this->runInfo("\nServer Started : " . date('Y-m-d H:i:s'));
        $this->runInfo('Listening on : ' . $port);
        $this->runInfo('Client : ' . $this->master . "\n");
        $this->connectPool[] = $this->master;

        while (true) {
            $sockets = $this->connectPool;
            $write = $except = null;
            if (socket_select($sockets, $write, $except, 60) < 1)
                continue;
            foreach ($sockets as $socket) {
                if ($socket === $this->master) {
                    $this->connectPool[] = $client = socket_accept($this->master);
                    $keyArr = array_keys($this->connectPool);
                    $key = end($keyArr);
                    $this->handPool[$key] = false;
                    $this->runInfo('Client ' . $client . " Connected!\n");
                } else {
                    $length = socket_recv($socket, $buffer, 2048, 0);
                    $key = array_search($socket, $this->connectPool);
                    if ($length < 7) {
                        $this->close($socket, $key);
                        continue;
                    } else {
                        if (!$this->handPool[$key]) {
                            $this->handShake($socket, $buffer, $key);
                        } else {
                            $message = $this->decode($buffer);
                            $this->parseMessage($message, $socket, $key);
                        }
                    }
                }
            }
        }
    }

    public function runInfo($msg)
    {
        echo $msg . PHP_EOL;
    }

    public function close($socket, $key)
    {
        socket_close($socket);
        unset($this->connectPool[$key]);
        unset($this->handPool[$key]);
        unset($this->chatUsers[$key]);
        $this->runInfo("Client {$socket} Disconnected!\n");
    }

    public function handShake($socket, $buffer, $key)
    {
        if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $buffer, $match)) {
            $responseKey = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
            $upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
                    "Upgrade: websocket\r\n" .
                    "Connection: Upgrade\r\n" .
                    "Sec-WebSocket-Accept: " . $responseKey . "\r\n\r\n";
            socket_write($socket, $upgrade, strlen($upgrade));
            $this->handPool[$key] = true;
        }
    }

    public function decode($buffer)
    {
        $len = $masks = $data = $decoded = null;
        $len = ord($buffer[1]) & 127;

        if ($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } elseif ($len === 127) {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } else {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }
        return $decoded;
    }

    public function parseMessage($message, $socket, $key)
    {
        $message = json_decode($message, true);
        switch ($message['type']) {
            case 1:
                $this->bind($key, $message);
                break;
            case 2:
                $this->sendToAll($message);
                /**$pdo = new \PDO("mysql:host=localhost;dbname=yongli","root","root");
                $sql = "insert into wwk_chat_msg values(null, '{$message['nickname']}', '{$message['username']}', '{$message['time']}', '{$message['time']}', '{$message['msg']}', '{$message['avatar']}', '0', '{$message['vip']}')";
                $pdo->exec($sql);*/
                break;
            case 3:
                $this->close($socket, $key);
            default:
                break;
        }
    }

    public function bind($key, $message)
    {
        $this->chatUsers[$key] = array(
            'username' => $message['username'],
			'nickname' => $message['nickname'],
            'avatar' => $message['avatar']
        );
    }

    public function sendToAll($message)
    {
        foreach ($this->connectPool as $socket) {
            if ($socket != $this->master) {
                $this->send($socket, $message);
            }
        }
    }

    public function send($socket, $message)
    {
        $message = $this->frame(json_encode($message));
        socket_write($socket, $message, strlen($message));
    }

    public function frame($message)
    {
        $len = strlen($message);
        if ($len <= 125) {
            return "\x81" . chr($len) . $message;
        } else if ($len <= 65535) {
            return "\x81" . chr(126) . pack("n", $len) . $message;
        } else {
            return "\x81" . char(127) . pack("xxxxN", $len) . $message;
        }
    }
 }